<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    body {
      padding: 100px;
    }
    #drag {
      position: relative;
      width: 200px;
      height: 200px;
      background: #afe;
    }
    #drop {
      width: 500px;
      height: 500px;
      border: 1px solid #ccc;
    }
    #dropinner {
      margin-top: 100px;
      width: 300px;
      height: 300px;
      border: 1px solid #ccc;
    }
  </style>
</head>
<body id="root">
<div id="drag" draggable="true" data-drop="true">drag</div>
<div id="drop" data-drop="true"><!--<div id="dropinner"></div>--></div>

<script>
  const $drag = document.querySelector('#drag')
  const $drop = document.querySelector('#drop')
  const $dropinner = document.querySelector('#dropinner')
  function on (dom, event, func) {
    return dom.addEventListener(event, func, false)
  }
  let $copy = null
  let $space = null
  let position = {
    x: 0,
    y: 0,
  }

  function createSpace () {
    const $space = document.createElement('div');
    $space.style.width = '100%'
    $space.style.height = '3px'
    $space.style.position = 'absolute'
    return $space
  }

  /*on($drag, 'drag', (ev) => {
    ev.preventDefault();
    ev.stopPropagation();
    console.log('drag', ev)
  });*/

  on($drag, 'dragstart', (ev) => {
    // ev.preventDefault();
    // ev.stopPropagation();

    // 深度克隆
    $copy = ev.target.cloneNode( true );
    console.log('dragstart', [$copy])
    const id = ~~(Math.random(1000) * 1000)
    $copy.setAttribute('data-id', id)
    $copy.style.border = '1px solid red'
    $copy.innerHTML = $copy.innerHTML + id
    console.log('dragstart', id)

    /*on($copy, 'dragover', (ev) => {
      ev.preventDefault();
      ev.stopPropagation();
    });*/
    on($copy, 'dragleave', (ev) => {
      ev.preventDefault();
      ev.stopPropagation();
      if (ev.target === $copy) {
        return;
      }

      if (ev.target.contains($copy) && $copy.parentNode === ev.target && isLeaver (ev)) {
        ev.target.removeChild($copy)
      }
    });

    on($copy, 'dragstart', function (ev) {
      // ev.preventDefault();
      ev.stopPropagation();

      /*on($copy.parentNode, 'dragend', function () {
        ev.preventDefault();
      })*/

      $copy = this;
      $copy.style.border = '1px solid red'
      // 深度克隆
      console.log('$copy dragstart', $copy)

      /*on(this, 'dragenter', function () {
        ev.preventDefault();
        ev.stopPropagation();
        console.log('$copy dragenter')
        if (!ev.target.contains($copy)) {
          ev.target.appendChild($copy)
        }
      });
      on(this, 'dragover', function () {
        ev.preventDefault();
        ev.stopPropagation();
        console.log('$copy dragover')
      });

      on(this, 'drop', function () {
        ev.preventDefault();
        ev.stopPropagation();
        $copy = null
        console.log('$copy drop')
      });*/
    });
  });

  /*on($drop, 'dragleave', (ev) => {
    // ev.preventDefault();
    // ev.stopPropagation();

    // ev.target.removeChild(copy)
    console.log('dragleave', ev)
  });*/

  /*on($dropinner, 'dragenter', (ev) => {
    ev.preventDefault();
    ev.stopPropagation();

    ev.target.appendChild(copy)
    console.log('dragenter', ev)
  });*/

  /*on($dropinner, 'dragleave', (ev) => {
    // ev.preventDefault();
    // ev.stopPropagation();

    // ev.target.removeChild(copy)
    console.log('dragleave', ev)
  });*/


  function isLeaver ({ x, y }) {
    const __x = x - position.x;
    const __y = y - position.y;

    return (__x <= 0 || __y <= 0 || __y >= position.h || __x >= position.w)
  }

  function appendChild ($dom) {
    $dom.appendChild($copy)
  }

  function insertBefore ($dom, referenceNode) {
    $dom.insertBefore($copy, referenceNode)
  }

  function removeChild ($dom) {
    if ($dom.contains($copy) && $copy.parentNode === $dom) {
      $dom.removeChild($copy)
    }
  }

  function delegate ($dom) {
    if ($dom.parentNode) {
      console.log('dragover', $dom.parentNode.dataset, $dom.parentNode.id)
      if ($dom.parentNode.id === 'root') {
        return
      }

      if ($dom.parentNode.dataset.drop) {
        return $dom.parentNode
      } else {
        return delegate($dom.parentNode)
      }
    }
  }

  on($drop, 'dragenter', (ev) => {
    ev.preventDefault();
    ev.stopPropagation();
    if (ev.target === $copy || ev.target === $copy.parentNode || $copy.contains(ev.target)) {
      return;
    }

    // console.log('dragenter', ev.target, $copy, $copy.contains(ev.target))

    position.x = ev.target.offsetLeft;
    position.h = ev.target.offsetHeight;
    position.y = ev.target.offsetTop;
    position.w = ev.target.offsetWidth;
    console.log('dragenter:==============', position)
    if ($copy.parentNode !== ev.target) {
      try {
        appendChild (ev.target)
      } catch (error) {
        console.error(error)
      }
    }
  });

  on($drop, 'dragleave', (ev) => {
    ev.preventDefault();
    ev.stopPropagation();
    const $related = ev.relatedTarget ||  ev.fromElement
    if (ev.target === $related) {
      return;
    }

    if (ev.target.contains($copy) && $copy.parentNode === ev.target && isLeaver (ev)) {
      removeChild (ev.target)
    }
  });



  on($drop, 'dragover', (ev) => {
    ev.preventDefault();
    ev.stopPropagation();
    const { x, y } = ev;
    const $related = ev.relatedTarget ||  ev.fromElement
    if (ev.target === $related || $copy.contains(ev.target)) {
      return;
    }

    if (x - position.x <=1 || y - position.y <=1) {
      const $parent = delegate (ev.target)
      // removeChild (ev.target)
      console.log('dragover', $parent)
      if ($parent && $copy.parentNode !== ev.target.parentNode) {
        console.log('dragover', $parent, ev.target.parentNode, ev.target)
        insertBefore ($parent, ev.target)
      }
      /*if ($parent) {
        insertBefore ($parent, ev.target)
      }*/
    } else if (!ev.target.contains($copy)) {
      appendChild (ev.target)
    }
  });

  on($drop, 'drop', (ev) => {
    ev.preventDefault();
    ev.stopPropagation();

    console.log('drop')
    // ev.target.appendChild($copy);

    $copy.style.border = ''
    $copy = null
  });
</script>
</body>
</html>
